Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F #he 948 improve calibration process #56

Open
wants to merge 25 commits into
base: noetic-devel
Choose a base branch
from

Conversation

carebare47
Copy link

@carebare47 carebare47 commented Jun 13, 2023

Proposed changes

Now runs faster / uses less compute

Fix progress bar logic

/tf callback was missing most messages due to callback execution time being too long. Now saves all messages and extracts a configurable subsample (can specify an approximate number of evenly distributed datapoints per second to keep)

Save result of previous sphere_fit as initial_guess for next sphere_fit (reduces computation time)

Add small sleep after tf subscriber, sometimes the subscriber warm-up time was longer than the first progress checkpoint, giving us empty lists which then got passed to np.std and threw numpy RuntimeWarnings

Added functionality to read from a rosbag for testing purposes

Publishing a MarkerArray instead of lots of Markers (saves cpu time)

Checklist

Before posting a PR ensure that from each of the below categories AT LEAST ONE BOX HAS BEEN CHECKED. If more than one category is applicable then more can be checked. Also ensure that the proposed changes have been filled out with relevant information for reviewers.

Tests

  • No tests required to be added. (For small changes that will be tested by CI/CD infrastructure).
  • Added/Modified automated and PhantomHand CI tests (if a new class is added (Python or C++), the interface of that class must be unit tested).
  • Manually tested in simulation (if simulation specific or no hardware required to test the functionality).
  • Manually tested on hardware (if hardware specific or related).

Documentation

  • No documentation required to be added.
  • Added documentation (For any new feature, explain what it does and how to use it. Write the documentation in a relevant space, e.g. Github, Confluence, etc).
  • Updated documentation (For changes to pre-existing features mentioned in the documentation).

carebare47 added 7 commits June 13, 2023 16:37
…e we actually have data points when we first call fit_sphere(...) which prevents three numpy RuntimeWarnings (we were sometimes calling np.std on an empty list)
…. Speeds up computation but potentially reduces accuracy. Needs testing
…r the next sphere_fit. Speeds up computation time
launch/start.launch Outdated Show resolved Hide resolved
@jeferal
Copy link

jeferal commented Jun 14, 2023

I reviewed and would accept, but first perhaps it would be useful to check how the calibration quality changes when changing the tf_data_divisor as we have discussed

@carebare47
Copy link
Author

I reviewed and would accept, but first perhaps it would be useful to check how the calibration quality changes when changing the tf_data_divisor as we have discussed

Agreed, checking that now

carebare47 added 3 commits June 14, 2023 16:47
…s execution time being too long (the number of msgs recieved was system-dependant and made it impossible to compare accuracy of calibration fits between runs). Modified code to save all tf messsages and process them in get_knuckle_positions. Added code to load a bag file for testing/tuning/comparing accuracy of calibration fits. Now that _load_tf_callback is recieving all the messages, the average number of msgs per second has gone up from ~15 to ~100, drastically increasing the computation of sphere fit. But, now we can add code to downsample to a KNOWN number of 'datapoints per second', giving the same performance across systems and lettings us actually figure out how many datapoints per second we need for a reasonable calibration fit
…pling to approximately a 'known' number of samples-per-second. Switched to MarkerArrays for faster marker publishing. Also general tidying
@carebare47
Copy link
Author

I reviewed and would accept, but first perhaps it would be useful to check how the calibration quality changes when changing the tf_data_divisor as we have discussed

It turns out that the /tf subscriber was missing the vast majority of messages because the callback was taking too long to execute, so it wasn't possible to compare/find a good "data divisor" number because we couldn't be sure that we were receiving the same messages every time (even from a bag file).

Added code to plug in a bag file of glove movements for testing purposes and moved the processing out of the callback so that the code catches every /tf message (either live or from a bag). Now, instead of dividing an unknown amount of incoming data, we capture all the data and subsample to give us approximately desired_number_of_datapoints_per_sec. Should be consistent across different machines now.

The bag file I've been testing with has ~120 messages per second.

Setting desired_number_of_datapoints_per_sec to above 120:

  • Calibration score of 96.2%
  • Sum of time taken to process tf data, publish markers and calculate the sphere fits: ~19 seconds

Setting desired_number_of_datapoints_per_sec to 20:

  • Calibration score of 96.3%
  • Sum of time taken to process tf data, publish markers and calculate the sphere fits: ~2.5 seconds

Setting desired_number_of_datapoints_per_sec to 10:

  • Calibration score of 96.2%
  • Sum of time taken to process tf data, publish markers and calculate the sphere fits: ~1.5 seconds

(Tests performed on a Ryzen 9 3900X)

So, we should tune this value when we test on an events laptop in spectrum house soon

@carebare47
Copy link
Author

while I remember, must update the rviz configs in sr_glove_calibration_gui before merging this. Will do this during testing

self._marker_array.markers.append(data_point_marker)

def get_number_of_markers(self):
return self._marker_array.markers.__len__()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although valid, why not

Suggested change
return self._marker_array.markers.__len__()
return len(self._marker_array.markers)

? Is the len method of marker_array does anything different than the len() operator on iterable?

def __init__(self, side: str = "right"):
def __init__(self, side: str = "right",
desired_datapoints_per_sec: int = 20,
testing_bag_file_path: str = ''):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually, when an argument is optional, we set it to None, because an empty string remains a string, which is the valid "type" of data.
Is there a reason you used the empty string?
If you want to change this to None, then this becomes

Suggested change
testing_bag_file_path: str = ''):
testing_bag_file_path: Optional[str] = None) -> None:

while adding from typing import Optional

def __init__(self, side: str = "right"):
def __init__(self, side: str = "right",
desired_datapoints_per_sec: int = 20,
testing_bag_file_path: str = ''):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
testing_bag_file_path: str = ''):
testing_bag_file_path: str = '') -> None:

to add the output type hint

@@ -73,10 +73,10 @@ class DataMarker(Marker):
""" Class to store data about a marker to be published to rviz. """
_id = 0

def __init__(self, frame_id: str, point: Point, color: ColorRGBA, size=0.002):
def __init__(self, frame_id: str, time_stamp: rospy.Time, point: Point, color: ColorRGBA, size=0.002):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding output type hint

Suggested change
def __init__(self, frame_id: str, time_stamp: rospy.Time, point: Point, color: ColorRGBA, size=0.002):
def __init__(self, frame_id: str, time_stamp: rospy.Time, point: Point, color: ColorRGBA, size=0.002) -> None:

self._marker_array = MarkerArray()
self._pub = rospy.Publisher(f"/data_point_marker_{self.hand_prefix}", MarkerArray, queue_size=1000)

def add_marker(self, frame_id: str, time_stamp: rospy.Time, point: Point, color: ColorRGBA):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def add_marker(self, frame_id: str, time_stamp: rospy.Time, point: Point, color: ColorRGBA):
def add_marker(self, frame_id: str, time_stamp: rospy.Time, point: Point, color: ColorRGBA) -> None:

self._action_server.publish_feedback(_feedback)

sub.unregister()
self._get_knuckle_positions(hand)
if self._testing_bag_file_path == '':
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self._testing_bag_file_path == '':
if self._testing_bag_file_path is None':


def _get_knuckle_positions(self, hand: Hand, plot=False):
def _get_knuckle_positions(self, hand: Hand, time_elapsed: float, plot=False):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing output type hint. I know it's not your fault, but this naming convention of _get_x that does not return anything (i.e. not a getter) confuses me a little... I think it would be better called _compute_knuckle_positions. What do you think?

"""
Updates the current solution for all fingers on the selected hand.
@param hand: Selected hand
@param time_elapsed: Time elapsed since the start of this calibration
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you describe what's the unit? seconds? milliseconds?

@@ -509,11 +619,16 @@ def get_calibration_quality(hand: Hand):
"""
quality_list: List[float] = []
for finger in fingers:
if len(hand.finger_data[finger]['residual']) == 0:
print('no data recieved')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print('no data recieved')
rospy.loginfo(f'No data received for finger {finger}')

@@ -509,11 +619,16 @@ def get_calibration_quality(hand: Hand):
"""
quality_list: List[float] = []
for finger in fingers:
if len(hand.finger_data[finger]['residual']) == 0:
print('no data recieved')
quality_list.append(np.std(hand.finger_data[finger]['residual']))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still add the standard deviation of a non-existing distribution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants